package br.cin.stadium.middleware.media;

import java.util.ArrayList;
import java.util.Hashtable;

import br.cin.stadium.middleware.directory.ServerLocation;
import br.cin.stadium.middleware.directory.StadiumDirectory;
import br.cin.stadium.middleware.event.Event;
import br.cin.stadium.middleware.event.EventCallbackReceiver;
import br.cin.stadium.middleware.event.EventMiddleware;
import br.cin.stadium.middleware.event.EventRemote;
import br.cin.stadium.middleware.event.EventRemoteListener;
import br.cin.stadium.middleware.marshall.Marshaller;
import br.cin.stadium.middleware.marshall.Serializable;
import br.cin.stadium.middleware.network.NetworkException;
import br.cin.stadium.middleware.network.NetworkHandler;
import br.cin.stadium.middleware.network.config.NetConfig;
import br.cin.stadium.middleware.util.Logger;

public class MediaManager implements EventCallbackReceiver {

	// List of medias available into the current device.
	private ArrayList<Media> medias = new ArrayList<Media>();
	
	private static MediaManager instance;
	
	ArrayList<ServerLocation> devices = null;
	
	private Hashtable<Integer, ArrayList<EventRemoteListener>> listenersCollection;
	
	private boolean processing = false;
	
	private MediaManager() {
		this.listenersCollection = new Hashtable<Integer, ArrayList<EventRemoteListener>>();
	}
	

	
	/**
	 * Returns the unique MediaManager instance that is running in the middleware.
	 * @return MediaManager The Unique Instance.
	 */
	public static MediaManager getInstance() {
		Logger.log("MediaManager", "getInstance", "");
		
		if(instance == null) {
			instance = new MediaManager();
		}
		
		return instance;
	}
	
	public void notifyListeners(EventRemote er) {
		Logger.log("MediaManager", "notifyListeners", "Begin(" + er.getMediaReference().getMediaName() + "," + er.getMediaReference().getSector() + ")");
		
		ArrayList<EventRemoteListener> listeners = this.listenersCollection.get(er.getMediaReference().getSector());

		Logger.log("===> LISTENER SIZE = " + ((listeners == null)?"NULL": listeners.size()));

		if(listeners != null) {
			for (int i = 0; i < listeners.size(); i++) {
				EventRemoteListener l = listeners.get(i);
				l.notify(er);
			}
		}
		
		Logger.log("MediaManager", "notifyListeners", "END");
	}
	
	public void addEventRemoteListener(EventRemoteListener l, int sector) {
		Logger.log("MediaManager", "addEventRemoteListener", "BEGIN(" + l + "," + sector + ")");
		
		ArrayList<EventRemoteListener> listeners = this.listenersCollection.get(sector);
		
		Logger.log("===> LISTENER SIZE = " + ((listeners == null)?"NULL": listeners.size()));
		
		if(listeners == null) {
			listeners = new ArrayList<EventRemoteListener>();
			this.listenersCollection.put(sector, listeners);
		}

		listeners.add(l);
		
		Logger.log("MediaManager", "addEventRemoteListener", "END");
	}

	/**
	 * Publish/write a media as a new entry into the tuple space.
	 * 
	 * 
	 * @param media
	 * @param stadiumSection
	 * 
	 * @return
	 */
	public boolean write(Media media) {
		Logger.log("MediaManager", "write", "BEGIN (" + media.getName() + "," + media.getStadiumSector() + "," + media.getContent().length + ")");

		medias.add(media);
		
		String myPort = Integer.toString(NetConfig.STADIUM_PORT_CLIENT_LISTEN);
		StadiumDirectory.getInstance().notifyDevices(new MediaReference(myPort, media.getName(), media.getStadiumSector()));
		
		Logger.log("MediaManager", "write", "END");
		return true;
	}

	/**
	 * 
	 * @param remoteHost
	 * @return
	 */
	public Media requestMedia(MediaReference mediaReference) {
		Logger.log("MediaManager", "requestMedia", "Begin (" + mediaReference.getMediaName() + "," + 
				                                               mediaReference.getSector() + "," +
				                                               mediaReference.getServerLocation().getIp() + "," +
				                                               mediaReference.getServerLocation().getPort() + ")");
		
		Media media = new Media();
		
		ServerLocation myLocation = new ServerLocation(StadiumDirectory.getInstance().getMyIP(), Integer.toString(NetConfig.STADIUM_PORT_CLIENT_LISTEN));

		if(mediaReference.getServerLocation().equals(myLocation)) {
			Media mediaTemp = this.getLocalMedia(mediaReference);
			
			if(mediaTemp != null) {
				media = mediaTemp;
			}
			
			
		} else {
			try {
				media = NetworkHandler.getInstance().requestMedia(mediaReference);
				medias.add(media);
				
			} catch (NetworkException e) {
				
				ArrayList<MediaReference> retorno = MediaManager.getInstance().read(mediaReference.getMediaName(), Event.OPCODE_READ_FROM_NAME);
				for(MediaReference ref : retorno) {
					
					if(mediaReference.getSector() == ref.getSector()) {
						try {
							if(ref.getServerLocation().equals(myLocation)) {
								Media mediaTemp = this.getLocalMedia(mediaReference);
								
								if(mediaTemp != null) {
									media = mediaTemp;
								}
							} else {
								media = NetworkHandler.getInstance().requestMedia(ref);
								medias.add(media);
								break;
							}
						} catch (NetworkException e1) {
							continue;
						}
					}
				}
			}
		}
		
		
		return media;
	}

	
	//=======================================================
	public ArrayList<MediaReference> read(Object value, int opCode) {
		Logger.log("MediaManager", "read", "Begin (" + value + "," + opCode + ")"); 

		ArrayList<MediaReference> retorno = new ArrayList<MediaReference>();

		// perform scan through network
		devices = new ArrayList<ServerLocation>();
		processing = false;
		this.retrieve();
		
		try {
			synchronized (this) {
				if(processing == false) {
					wait();
				}
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		// Go through the devices searching for mediaName
		ArrayList<MediaReference> search = null;
		
		ServerLocation myLocation = new ServerLocation(StadiumDirectory.getInstance().getMyIP(), Integer.toString(NetConfig.STADIUM_PORT_CLIENT_LISTEN));
		for (ServerLocation stadiumUnit : devices) {
			search = null;
			// request media references that match media name
			// If the device is this one, make a simple search
			if (stadiumUnit.equals(myLocation)) {
				search = searchForMediaReference(opCode, value);
				if (search != null && !search.isEmpty()) {
					for (int i = 0; i < search.size(); i++) {
						search.get(i).getServerLocation().setIp(stadiumUnit.getIp());
					}

					retorno.addAll(search);
				}
			}
			// connect to each other
			else {
				ArrayList<Object> values = new ArrayList<Object>();
				values.add(opCode);
				values.add(value);
				
				try {
					search = NetworkHandler.getInstance().connectAndsearchForMedia(stadiumUnit,values);

					if (search != null && !search.isEmpty()) {
						for (int i = 0; i < search.size(); i++) {
							search.get(i).getServerLocation().setIp(stadiumUnit.getIp());
						}
						
						retorno.addAll(search);
					}
					
				} catch(NetworkException ne) {
					Logger.logError(ne.getMessage(), ne);
				}
			}
		}
		
		devices = null;

		Logger.log("MediaManager", "read", "End (" + retorno.size() + ")"); 
		return retorno;
	}


	// ====================================================
	public ArrayList<MediaReference> searchForMediaReference(int opCode, Object value) {
		Logger.log("MediaManager", "searchForMediaReference", "Begin (" + opCode + "," + value + ")"); 

		ArrayList<MediaReference> mr = new ArrayList<MediaReference>();
		
		String myPort = Integer.toString(NetConfig.STADIUM_PORT_CLIENT_LISTEN);
		
		if(opCode == Event.OPCODE_READ_ALL_MEDIA) {
			for (Media media : medias) {
				mr.add(new MediaReference(myPort, media.getName(), media.getStadiumSector()));
			}
			
		} else if(opCode == Event.OPCODE_READ_FROM_SECTOR) {
			int sector = ((Integer) value).intValue();
			for (Media media : medias) {
				if (media.getStadiumSector() == sector) {
					mr.add(new MediaReference(myPort, media.getName(), media.getStadiumSector()));
				}
			}
			
		} else if(opCode == Event.OPCODE_READ_FROM_NAME) {
			String name = value.toString();
			for (Media media : medias) {
				if (media.getName().equalsIgnoreCase(name)) {
					mr.add(new MediaReference(myPort, name, media.getStadiumSector()));
				}
			}
		} else {
			//Thrown an exception
		}
		
		Logger.log("MediaManager", "searchForMediaReference", "End (" + mr.size() + ")"); 
		return mr;
	}
	
	// ====================================================

	public Media searchForMedia(String name) {
		Logger.log("MediaManager", "searchForMedia", "Begin (" + name + ")"); 

		Media retorno = null;
		for (Media media : medias) {
			if (media.getName().equalsIgnoreCase(name)) {
				retorno = media;
				break;
			}
		}

		Logger.log("MediaManager", "searchForMedia", "End (" + retorno.getName() + "," + 
				                                               retorno.getStadiumSector() + "," + 
				                                               retorno.getType() +  "," + 
				                                               retorno.getContent().length + ")"); 
		return retorno;
	}

	// ====================================================

	/**
	 * Get a list of stadiumUnits registered into the StadiumDirectory
	 * 
	 * @return A list of stadiumUnits registered into the StadiumDirectory
	 */
	private void retrieve() {
		Logger.log("MediaManager", "retrieve", "Begin");
		
		StadiumDirectory.getInstance().retrieve(this);
		
		Logger.log("MediaManager", "retrieve", "End(" + devices.size() + ")");
	}
	
	/* (non-Javadoc)
	 * @see br.cin.stadium.middleware.event.EventCallbackReceiver#handleEvent(br.cin.stadium.middleware.event.EventMiddleware)
	 */
	public void handleEvent(EventMiddleware e) {
		Logger.log("MediaManager", "handleEvent", "Begin (" + e.getOpCode() + ")");
		
		switch(e.getOpCode()) {
			case Event.OPCODE_RETRIEVE_DEVICES:
				byte[] data = e.getData();
				
				Serializable template = new ServerLocation();
				Marshaller.unmarshall(template, devices, data);
				synchronized (this) {
					processing = true;
					notifyAll();			
				}
	
				break;
		}



		Logger.log("MediaManager", "handleEvent", "End(" + ((devices!=null)?devices.size():null) + ")");
	}
	
	private Media getLocalMedia(MediaReference ref) {
		Media retorno = null;
		
		for (Media m : this.medias) {
			if(m.getName().equals(ref.getMediaName()) && m.getStadiumSector() == ref.getSector()) {
				retorno = m;
				break;
			}
		}
		
		return retorno;
	}
	
}
